#pragma region Licensing
/*
       LOGL::GLib -> LorgonJortle's OpenGL Game Library
       Copyright (C) 2011  Jesse 'LorgonJortle' Wilkerson

       This program is free software: you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation, either version 3 of the License, or
       (at your option) any later version.

       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.

       You should have received a copy of the GNU General Public License
       along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#pragma endregion

#include <string.h>

#include "ShaderManager.h"

namespace LOGL_GLib
{
     using namespace Utilities;

     namespace Common
     {
          /******************************************************************************
           ShaderAttributes::ShaderAttributes();
                    Arguments:
                         void

                    Description:
                         Constructs a ShaderAttributes object.

                    Returns:
                         The ShaderAttributes object.
          *******************************************************************************/
          ShaderAttributes::ShaderAttributes()
          {
               /* Set defaults. */
               UsedAttributes = 0;
               memset(Attributes, 0, sizeof(Attributes));
          }

          /******************************************************************************
           GLvoid ShaderAttributes::SetAttribute(Attribute _Attribute, GLchar* _Name);
                    Arguments:
                         Attribute _Attribute:
                              The attribute to work on.
                         GLchar* _Name:
                              The name of the attribute as it appears in the vertex program.

                    Description:
                         Sets the specified attribute's name as it appears in the shader program.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid ShaderAttributes::SetAttribute(Attribute _Attribute, GLchar* _Name)
          {
               /* If we're setting a new attribute, increase the count. */
               if(Attributes[_Attribute] == GL_FALSE)
                    UsedAttributes++;

               Attributes[_Attribute] = GL_TRUE;
               AttributeNames[_Attribute] = _Name;
          }

          /******************************************************************************
           ShaderManager::ShaderManager();
                    Arguments:
                         void

                    Description:
                         Constructs a ShaderManager.

                    Returns:
                         The ShaderManager object.
          *******************************************************************************/
          ShaderManager::ShaderManager()
          {
               nextID = 1;
        }

        /******************************************************************************
           GLuint ShaderManager::Add(Shader& _Shader);
                    Arguments:
                         Shader& _Shader:
                        The shader object to add to the manager.

                Description:
                         Adds the specified shader to the manager.

                    Returns:
                         The shader's ID in respect to the manager.
          *******************************************************************************/
          GLuint ShaderManager::Add(Shader& _Shader)
          {
               /* Set the shader's ID. */
               _Shader.ID = nextID;

               /* Add the new shader. */
               shaders.push_back(_Shader);

               /* Return the next ID, then increment it. */
               return nextID++;
          }

        /******************************************************************************
           GLvoid ShaderManager::Remove(GLuint _ShaderID);
                    Arguments:
                         GLuint _ShaderID:
                        The ID of the shader object to remove from the manager.

                Description:
                         Removes the specified shader from the manager.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid ShaderManager::Remove(GLuint _ShaderID)
          {
            /* Look for the shader. */
               for(std::deque<Shader>::iterator i = shaders.begin(); i != shaders.end(); i++)
               {
                   /* If we find it, delete it. Otherwise... it doesn't exist. */
                    if(i->ID == _ShaderID)
                {
                         shaders.erase(i);
                         break;
                }
               }
        }

          /******************************************************************************
           GLboolean ShaderManager::Bind(GLuint _ShaderID);
                    Arguments:
                         GLuint _ShaderID:
                              The ID of the shader program to bind to the OpenGL state.

                    Description:
                         Binds/Enables the specified (already loaded) shader program.

                    Returns:
                         Whether or not it was able to bind the specified program.
          *******************************************************************************/
          GLboolean ShaderManager::Bind(GLuint _ShaderID)
          {
               /* Search for that shader. */
               for(std::deque<Shader>::iterator i = shaders.begin(); i != shaders.end(); i++)
               {
                    if(i->ID == _ShaderID)
                    {
                         /* We've found it, so let's use the program. */
                         glUseProgram(i->ProgramID);

                         return GL_TRUE;
                    }
               }

               /* We didn't find it. */
               return GL_FALSE;
          }

          /******************************************************************************
           GLvoid ShaderManager::Unbind() const;
                    Arguments:
                         void

                    Description:
                         Unbinds any programs and returns to fixed-functionality.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid ShaderManager::Unbind() const
          {
               glUseProgram(0);
          }

        /******************************************************************************
           Shader* const ShaderManager::At(GLuint _ShaderID);
                    Arguments:
                         GLuint _ShaderID:
                        The ID of the shader object to access.

                    Description:
                         Gets a pointer to the specified shader.

                    Returns:
                         A pointer to the specified shader.
          *******************************************************************************/
          Shader* const ShaderManager::At(GLuint _ShaderID)
          {
               /* Look for the shader. */
               for(std::deque<Shader>::iterator i = shaders.begin(); i != shaders.end(); i++)
               {
                    if(i->ID == _ShaderID)
                         return &(*i);
               }

               /* We didn't find it. Return NULL. */
               return 0;
          }

          /******************************************************************************
           Shader::Shader();
                    Arguments:
                         void

                    Description:
                         Constructs a shader object.

                    Returns:
                         The Shader object.
          *******************************************************************************/
          Shader::Shader()
          {
               Name = "LOGL::GLib Shader";
               VertexProgramID = 0;
               GeometryProgramID = 0;
               FragmentProgramID = 0;
               ProgramID = 0;
               ID = 0;
          }

          /******************************************************************************
           Shader::Shader(const std::string& _Name);
                    Arguments:
                         const std::string _Name:
                              The name to give the shader program.

                    Description:
                         Constructs a Shader object with the specified name.

                    Returns:
                         The Shader object.
          *******************************************************************************/
          Shader::Shader(const std::string& _Name)
          {
               Name = _Name;
               VertexProgramID = 0;
               GeometryProgramID = 0;
               FragmentProgramID = 0;
               ProgramID = 0;
               ID = 0;

               Attributes.SetAttribute(Attribute_Position, "Position");
               Attributes.SetAttribute(Attribute_Texture0, "TexCoord0");
               Attributes.FragmentAttribute = "FragColor";
          }

          /******************************************************************************
           ShaderManager::Shader::~Shader();
                    Arguments:
                         void

                    Description:
                         Destructs the shader object and release any indebted resources.

                    Returns:
                         void
          *******************************************************************************/
          Shader::~Shader()
          {
               if(VertexProgramID != -1)
                    {
                         glDetachShader(ProgramID, VertexProgramID);
                         glDeleteShader(VertexProgramID);
                    }
                    if(GeometryProgramID != -1)
                    {
                         glDetachShader(ProgramID, GeometryProgramID);
                         glDeleteShader(GeometryProgramID);
                    }
                    if(FragmentProgramID != -1)
                    {
                         glDetachShader(ProgramID, FragmentProgramID);
                         glDeleteShader(FragmentProgramID);
                    }
                    if(ProgramID != -1)
                         glDeleteProgram(ProgramID);
          }

          /******************************************************************************
           GLboolean ShaderManager::LoadShader(const ShaderFormat _Format, const GLchar* _VertexFile = 0,
                                    const GLchar* _GeometryFile = 0, const GLchar* _FragmentFile = 0);
                    Arguments:
                    const ShaderFormat _Format:
                              The format of the shader we're loading.
                         const GLchar* _VertexFile:
                              The location (or data) of the vertex shader program.
                    const GLchar* _GeometryFile:
                        The location (or data) of the geometry shader program.
                         const GLchar* _FragmentFile:
                              The location (or data) of the fragment shader program.

                    Description:
                         Loads the specified shader program.

                    Returns:
                         Whether or not the shader successfully loaded.
          *******************************************************************************/
          GLboolean Shader::Load(const ShaderFormat _Format, const GLchar* _VertexFile = 0, const GLchar* _GeometryFile = 0, const GLchar* _FragmentFile = 0)
          {
               /* The result of the loading. */
               GLboolean result = GL_TRUE;

               try
               {
                    /* Load any shaders that were specified. */
                    if(_VertexFile != 0)
                         result &= LoadVertexProgram(_Format, _VertexFile);
                    if(_GeometryFile != 0)
                         result &= LoadGeometryProgram(_Format, _GeometryFile);
                    if(_FragmentFile != 0)
                         result &= LoadFragmentProgram(_Format, _FragmentFile);
               }
               catch(...)
               {
                    return GL_FALSE;
               }

               return result;
          }

          /******************************************************************************
           GLboolean Shader::LoadVertexProgram(ShaderFormat _Format, const GLchar* _VertexFile);
                    Arguments:
                    const ShaderFormat _Format:
                              The format the shader program is in.
                         const GLchar* _VertexFile:
                              The location (or data) of the vertex shader program.

                    Description:
                         Loads a vertex shader program from the specified fies.

                    Returns:
                         Whether or not the shader program loaded properly.
          *******************************************************************************/
          GLboolean Shader::LoadVertexProgram(ShaderFormat _Format, const GLchar* _VertexFile)
          {
               /* Create our shader object. If 0 is returned, there's been an error. */
               VertexProgramID = glCreateShader(GL_VERTEX_SHADER);

               if(VertexProgramID <= 0)
                    return GL_FALSE;

               switch(_Format)
               {
               case FromFile:
                    {
                         if(!SourceFromFile(_VertexFile, VertexProgramID))
                         {
                              glDeleteShader(VertexProgramID);
                              return GL_FALSE;
                         }
                    } break;
               case FromMemory:
                    {
                         /* Set the source for our program. */
                         GLchar* sourceArray[1];
                         sourceArray[0] = (GLchar*)_VertexFile;
                         glShaderSource(VertexProgramID, 1, (const GLchar **)sourceArray, 0);
                    } break;
               default:
                    {
                         return GL_FALSE;
                    } break;
               }

               /* Our vertex program has been loaded! */
               return GL_TRUE;
          }

        /******************************************************************************
           GLboolean Shader::LoadGeometryProgram(ShaderFormat _Format, const GLchar* _GeometryFile);
                    Arguments:
                    const ShaderFormat _Format:
                              The format the shader program is in.
                         const GLchar* _GeometryFile:
                              The location (or data) of the geometry shader program.

                    Description:
                         Loads a geometry shader program from the specified fies.

                    Returns:
                         Whether or not the shader program loaded properly.
          *******************************************************************************/
          GLboolean Shader::LoadGeometryProgram(ShaderFormat _Format, const GLchar* _GeometryFile)
          {
               /* Create our shader object. If 0 is returned, there's been an error. */
               GeometryProgramID = glCreateShader(GL_GEOMETRY_SHADER);

               if(GeometryProgramID <= 0)
                    return GL_FALSE;

               switch(_Format)
               {
               case FromFile:
                    {
                         if(!SourceFromFile(_GeometryFile, GeometryProgramID))
                         {
                              glDeleteShader(GeometryProgramID);
                              return GL_FALSE;
                         }
                    } break;
               case FromMemory:
                    {
                         /* Set the source for our program. */
                         GLchar* sourceArray[1];
                         sourceArray[0] = (GLchar*)_GeometryFile;
                         glShaderSource(GeometryProgramID, 1, (const GLchar **)sourceArray, 0);
                    } break;
               default:
                    {
                         return GL_FALSE;
                    } break;
               }

               /* Our geometry program has been loaded! */
               return GL_TRUE;
          }

        /******************************************************************************
           GLboolean Shader::LoadFragmentProgram(ShaderFormat _Format, const GLchar* _FragmentFile);
                    Arguments:
                    const ShaderFormat _Format:
                              The format the shader program is in.
                         const GLchar* _FragmentFile:
                              The location (or data) of the fragment shader program.

                    Description:
                         Loads a fragment shader program from the specified fies.

                    Returns:
                         Whether or not the shader program loaded properly.
          *******************************************************************************/
          GLboolean Shader::LoadFragmentProgram(ShaderFormat _Format, const GLchar* _FragmentFile)
          {
               /* Create our shader object. If 0 is returned, there's been an error. */
               FragmentProgramID = glCreateShader(GL_FRAGMENT_SHADER);

               if(FragmentProgramID <= 0)
                    return GL_FALSE;

               switch(_Format)
               {
               case FromFile:
                    {
                         if(!SourceFromFile(_FragmentFile, FragmentProgramID))
                         {
                              glDeleteShader(FragmentProgramID);
                              return GL_FALSE;
                         }
                    } break;
               case FromMemory:
                    {
                         /* Set the source for our program. */
                         GLchar* sourceArray[1];
                         sourceArray[0] = (GLchar*)_FragmentFile;
                         glShaderSource(FragmentProgramID, 1, (const GLchar **)sourceArray, 0);
                    } break;
               default:
                    {
                         return GL_FALSE;
                    } break;
               }

               /* Our geometry program has been loaded! */
               return GL_TRUE;
          }

          /******************************************************************************
           GLboolean Shader::SourceFromFile(const std::string& _File,
                                        GLint _ShaderID);
                    Arguments:
                         const std::string& _File:
                              The file to read in and set as the program's source.
                         GLint _ShaderID:
                              The handle of the shader to set the source of.

                    Description:
                         Loads the entire specified file and sets it as the program's source.

                    Returns:
                         Whether or not the program's source was loaded properly.
          *******************************************************************************/
          GLboolean Shader::SourceFromFile(const std::string& _File, GLint _ShaderID)
          {
               std::ifstream fileStream;
               std::string source = "";
               std::string temp = "";

               /* Open our file so we can read from it. */
               fileStream.open(_File.c_str());
               if(!fileStream.is_open())
                    return GL_FALSE;

               /* While there's data left to read, add it to the source string. */
               while(!fileStream.eof())
               {
                    std::getline(fileStream, temp);
                    source += temp;
                    source += "\n";
               }

               /* Close the file after we're done reading. */
               fileStream.close();

               if(source.length() == 0)
                    return GL_FALSE;

               /* Make sure the source is null-terminated. */
               source += "\0";

               /* Get the source into an array compatible with glShaderSource and pass it along. */
               GLchar* sourceArray[1];
               sourceArray[0] = (GLchar*)source.c_str();

               glShaderSource(_ShaderID, 1, (const GLchar **)sourceArray, 0);

               /* We made it! */
               return GL_TRUE;
          }

          /******************************************************************************
           GLboolean Shader::Compiler();
                    Arguments:
                         void

                    Description:
                         Compiles and links the shader's source.

                    Returns:
                         Whether or not the program's source was compiled/linked properly.
          *******************************************************************************/
          GLboolean Shader::Compile()
          {
               /* We need to check if there were any errors in any programs. */
               GLint result = GL_FALSE;

               if(VertexProgramID > 0)
               {
                    glCompileShader(VertexProgramID);

                    /* Check the vertex program. */
                    glGetShaderiv(VertexProgramID, GL_COMPILE_STATUS, &result);
                    if(result == GL_FALSE)
                    {
                         glGetShaderInfoLog(VertexProgramID, sizeof(Log), 0, Log);
                         glDeleteShader(VertexProgramID);

                         return GL_FALSE;
                    }

                    /* Reset the compile flag. */
                    result = GL_FALSE;
               }
               if(GeometryProgramID > 0)
               {
                    glCompileShader(GeometryProgramID);

                    /* Check the geometry program. */
                    glGetShaderiv(GeometryProgramID, GL_COMPILE_STATUS, &result);
                    if(result == GL_FALSE)
                    {
                         glGetShaderInfoLog(GeometryProgramID, sizeof(Log), 0, Log);
                         glDeleteShader(VertexProgramID);
                         glDeleteShader(GeometryProgramID);

                         return GL_FALSE;
                    }

                    /* Reset the compile flag. */
                    result = GL_FALSE;
               }
               if(FragmentProgramID > 0)
               {
                    glCompileShader(FragmentProgramID);

                    /* Check the fragment program. */
                    glGetShaderiv(FragmentProgramID, GL_COMPILE_STATUS, &result);
                    if(result == GL_FALSE)
                    {
                         glGetShaderInfoLog(FragmentProgramID, 1024, 0, Log);
                         glDeleteShader(VertexProgramID);
                         glDeleteShader(GeometryProgramID);
                         glDeleteShader(FragmentProgramID);

                         return GL_FALSE;
                    }

                    /* Reset the compile flag. */
                    result = GL_FALSE;
               }

               /* Create the shader program. */
               ProgramID = glCreateProgram();

               /* Attach any shaders we've compiled. */
               if(VertexProgramID > 0)
                    glAttachShader(ProgramID, VertexProgramID);
               if(GeometryProgramID > 0)
                    glAttachShader(ProgramID, GeometryProgramID);
               if(FragmentProgramID > 0)
                    glAttachShader(ProgramID, FragmentProgramID);

               /* Bind the fragment attribute. */
               if(Attributes.FragmentAttribute.length() > 0)
                    glBindFragDataLocation(ProgramID, 0, Attributes.FragmentAttribute.c_str());

               /* Bind all vertex attributes. */
               for(GLint i = 0; i < Attributes.UsedAttributes; i++)
               {
                    if(Attributes.Attributes[i] == GL_TRUE)
                    {
                         glBindAttribLocation(ProgramID, i, Attributes.AttributeNames[i].c_str());
                    }
               }

               /* Let's link the program (or try to...)! */
               glLinkProgram(ProgramID);

               /* Finally, just check if the linking worked or not. */
               glGetProgramiv(ProgramID, GL_LINK_STATUS, &result);
               if(result == GL_FALSE)
               {
                    glGetProgramInfoLog(ProgramID, 1024, 0, Log);

                    glDetachShader(ProgramID, VertexProgramID);
                    glDeleteShader(VertexProgramID);

                    glDetachShader(ProgramID, GeometryProgramID);
                    glDeleteShader(GeometryProgramID);

                    glDetachShader(ProgramID, FragmentProgramID);
                    glDeleteShader(FragmentProgramID);

                    glDeleteProgram(ProgramID);
                    return GL_FALSE;
               }

               /* We've compiled and linked! */
               return GL_TRUE;
          }

          /******************************************************************************
           GLint ShaderManager::GetUniformLocation(const std::string& _Uniform);
                    Arguments:
                         const std::string _Uniform:
                              The name of the uniform in the bound program to query.

                    Description:
                         Gets the location of the specified uniform in the currently bound program.

                    Returns:
                         The location of the specified uniform in the currently bound program.
          *******************************************************************************/
          GLint Shader::GetUniformLocation(const std::string& _Uniform)
          {
               return glGetUniformLocation(ProgramID, _Uniform.c_str());
          }

     } /* Namespace Common. */
} /* Namespace LOGL_GLib. */
